package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/mux"
	"github.com/patrickmn/go-cache"
	uuid "github.com/satori/go.uuid"
	"net/http"
	"strings"
	"time"
)

type WeChatConfig struct {
	WeChatWork *WechatWorkConfig `json:"work"`
}

type WechatWorkConfig struct {
	CorpID string `json:"appid"`
	AgentID string `json:"agentid"`
	Secret string `json:"secret"`
}

type BaseAPIResponse struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
}

type AcccessTokenResp struct {
	BaseAPIResponse
	Token  string `json:"access_token"`
	Expire int    `json:"expires_in"`
}

type UserInfoResp struct {
	BaseAPIResponse
	UserID   string `json:"UserId"`
	DeviceID string `json:"DeviceId"`
}

type WeChatWorkUser struct {
	config *WechatWorkConfig
	cache *cache.Cache
}

func NewWeChatWorkUser(conf *WechatWorkConfig) *WeChatWorkUser {
	return &WeChatWorkUser{
		config: conf,
		cache: cache.New(time.Minute*5, time.Minute*10),
	}
}

func (this *WeChatWorkUser) getAccessToken() (string, error) {
	_token, found := this.cache.Get(`access_token`)
	if found {
		token, ok := _token.(string)
		if ok {
			return token, nil
		}
	}

	httpClient := http.Client{
		Timeout: time.Second*10,
	}
	resp, err := httpClient.Get(fmt.Sprintf(`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s`, this.config.CorpID, this.config.Secret))
	if err != nil {
		log.Error(err)
		return "", err
	}
	defer resp.Body.Close()

	var respBody AcccessTokenResp
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&respBody)
	if err != nil {
		log.Error(err)
		return "", err
	}

	if respBody.ErrCode != 0 {
		return "", errors.New(respBody.ErrMsg)
	}

	this.cache.Set(`access_token`, respBody.Token, (time.Second*time.Duration(respBody.Expire)))

	return respBody.Token, nil
}

func (this *WeChatWorkUser) GetUserInfo(code string) (userID string, err error) {
	httpClient := http.Client{
		Timeout: time.Second*10,
	}

	token, err := this.getAccessToken()
	if err != nil {
		log.Error(err)
		return "", err
	}

	resp, err := httpClient.Get(fmt.Sprintf(`https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s`, token, code))
	if err != nil {
		log.Error(err)
		return "", err
	}
	defer resp.Body.Close()

	var respBody UserInfoResp
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&respBody)
	if err != nil {
		log.Error(err)
		return "", err
	}

	if respBody.ErrCode != 0 {
		return "", errors.New(respBody.ErrMsg)
	}

	return respBody.UserID, nil
}

func (this *WeChatWorkUser) GetURLKey(URL string) string {
	uuid := fmt.Sprintf(`%s`, uuid.NewV4())

	this.cache.Set(uuid, URL, time.Minute*10)

	return uuid
}

func (this *WeChatWorkUser) GetCacheURL(key string) (URL string, found bool) {
	val, found := this.cache.Get(key)
	if found {
		URL, ok := val.(string)
		if ok {
			return URL, true
		}
	}

	return "", false
}

func (this *WeChatWorkUser) GetQrcodeURL(authURL string, redirectURL string) string {
	hashKey := this.GetURLKey(redirectURL)
	return fmt.Sprintf(`https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=%s&agentid=%s&redirect_uri=%s&state=%s`, this.config.CorpID, this.config.AgentID, authURL, hashKey)
}

func (this *WeChatWorkUser) GetLoginURL(authURL string, redirectURL string) string {
	hashKey := this.GetURLKey(redirectURL)
	return fmt.Sprintf(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect`, this.config.CorpID, authURL, hashKey)
}

func (this *WeChatWorkUser) ResponseError(err error, writer http.ResponseWriter) {
	buff := new(bytes.Buffer)
	encoder := json.NewEncoder(buff)
	encoder.Encode(JSONResult{
		Status: 500,
		Error: err.Error(),
	})
	writer.Header().Set("Content-Type", "application/json")
	http.Error(writer, buff.String(), 500)
}

func (this *WeChatWorkUser) addCookie(w http.ResponseWriter, name, value string, ttl time.Duration) {
	expire := time.Now().Add(ttl)
	cookie := http.Cookie{
		Name:    name,
		Value:   value,
		Expires: expire,
		Path: `/`,
	}
	http.SetCookie(w, &cookie)
}

func (this *WeChatWorkUser) handleAuthCallBack(writer http.ResponseWriter, request *http.Request) {
	code := request.FormValue("code")
	state := request.FormValue("state")

	userID, err := this.GetUserInfo(code)
	if err != nil {
		this.ResponseError(err, writer)
		return
	}

	if len(userID) == 0 {
		this.ResponseError(errors.New(`user not found`), writer)
		return
	}

	redirectURL, found := this.GetCacheURL(state)
	if found {
		this.cache.Set(state, userID, time.Hour*24)
		this.addCookie(writer, `ipa2s3-session`, state, time.Hour*24)
		http.Redirect(writer, request, redirectURL, 302)
	}
}

func (this *WeChatWorkUser) AuthMiddleware(prefix string) mux.MiddlewareFunc {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

			if strings.HasPrefix(r.URL.Path, prefix) {
				cookie, err := r.Cookie(`ipa2s3-session`)

				authURL := fmt.Sprintf(`http://%s/auth/wechat/work/callback`, r.Host)
				qrcodeURL := this.GetQrcodeURL(authURL, r.URL.String())

				if err != nil {
					log.Error(err)
					http.Redirect(w, r, qrcodeURL, 302)
					return;
				}
				_, found := this.cache.Get(cookie.Value)
				if !found {
					log.Error(`userID not found`)
					http.Redirect(w, r, qrcodeURL, 302)
					return
				}
			}


			next.ServeHTTP(w, r)
		})
	}
}
